package main;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class ClasspathPackageScanner {


    private String basePackageName;
    private ClassLoader classLoader;


    public ClasspathPackageScanner(String basePackageName) {
        this.basePackageName = basePackageName;
        this.classLoader = getClass().getClassLoader();

    }

    public ClasspathPackageScanner(String basePackageName, ClassLoader classLoader) {
        this.basePackageName = basePackageName;
        this.classLoader = classLoader;
    }


    public static void main(String[] args) throws Exception {
        ClasspathPackageScanner classpathPackageScanner = new ClasspathPackageScanner("beautiful.butterfly");
        classpathPackageScanner.getFullyQualifiedClassNameList();
    }


    public List<String> getFullyQualifiedClassNameList() throws IOException {
        return doScan(basePackageName, new ArrayList<String>());
    }

    private List<String> doScan(String basePackageName, List<String> nameList) throws IOException {
        // replace dots with splashes
        String basePackageNameWithSplash = StringUtil.dotToSplash(basePackageName);//  . -> /

        // get file path
        URL url = classLoader.getResource(basePackageNameWithSplash);
        String filePath = StringUtil.getRootPath(url);

        // Get classes in that package.
        // If the web handlers unzips the jar file, then the classes will exist in the form of
        // normal file in the directory.
        // If the web handlers does not unzip the jar file, then classes will exist in jar file.
        List<String> names = null; // contains the name of the class file. e.g., Apple.class will be stored as "Apple"
        if (isJarFile(filePath)) {
            // jar file

            names = readFromJarFile(filePath, basePackageNameWithSplash);
        } else {

            names = readFromDirectory(filePath);
        }

        for (String name : names) {
            if (isClassFile(name)) {
                //nameList.add(basePackageName + "." + StringUtil.trimExtension(name));
                nameList.add(toFullyQualifiedName(name, basePackageName));
            } else {
                // this is a directory
                // check this directory for more classes
                // do recursive invocation
                doScan(basePackageName + "." + name, nameList);
            }
        }


        return nameList;
    }

    private String toFullyQualifiedName(String shortName, String basePackage) {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(StringUtil.trimExtension(shortName));

        return sb.toString();
    }

    private List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {

        JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarPath));
        JarEntry jarEntry = jarInputStream.getNextJarEntry();

        List<String> list = new ArrayList<>();
        while (null != jarEntry) {
            String name = jarEntry.getName();
            if (name.startsWith(splashedPackageName) && isClassFile(name)) {
                list.add(name);
            }
            jarEntry = jarInputStream.getNextJarEntry();
        }
        return list;
    }

    private List<String> readFromDirectory(String path) {
        File file = new File(path);
        String[] list = file.list();
        if (null == list) {
            return null;
        }
        return Arrays.asList(list);
    }

    private boolean isClassFile(String name) {
        return name.endsWith(".class");
    }

    private boolean isJarFile(String name) {
        return name.endsWith(".jar");
    }

}